//
//  HCLogManager.m
//  HCLog
//
//  Created by 韩昌 on 2017/7/11.
//  Copyright © 2017年 Han Chang. All rights reserved.
//

#import "HCLogManager.h"
#import "HCLoggerNode.h"

static void *const LoggingQueueKey = (void *)&LoggingQueueKey;
static const int kLogMaxQueueSize = 512;

@interface HCLogManager ()
{
    dispatch_queue_t _loggingQueue;
    
    dispatch_group_t _loggingGroup;
    
    dispatch_semaphore_t _loggingQueueSemaphore;
    
    NSMutableArray *_loggers;
}

@end

@implementation HCLogManager

+ (instancetype)sharedInstance
{
    static id sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    
    return sharedInstance;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        _loggingQueue = dispatch_queue_create("org.hanchang.log.loggingQueue", NULL);
        _loggingGroup = dispatch_group_create();
        
        dispatch_queue_set_specific(_loggingQueue, LoggingQueueKey, LoggingQueueKey, NULL);
        
        _loggingQueueSemaphore = dispatch_semaphore_create(kLogMaxQueueSize);
        
        _loggers = [NSMutableArray array];
        
        NSString *notificationName = @"UIApplicationWillTerminateNotification";
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationWillTerminate:)
                                                     name:notificationName
                                                   object:nil];
    }
    return self;
}

#pragma mark - Notifications

- (void)applicationWillTerminate:(NSNotification *)notification
{
    [self flushLog];
}

- (dispatch_queue_t)loggingQueue
{
    return _loggingQueue;
}

- (void)addLogger:(id<HCLogger>)logger level:(HCLogLevel)level
{
    if (!logger) {
        return;
    }
    
    dispatch_async(_loggingQueue, ^{ @autoreleasepool {
        [self lq_addLogger:logger level:level];
    } });
}

- (void)removeLogger:(id<HCLogger>)logger
{
    if (!logger) {
        return;
    }
    
    dispatch_async(_loggingQueue, ^{ @autoreleasepool {
        [self lq_removeLogger:logger];
    } });
}

- (void)logWithFlag:(HCLogFlag)flag context:(NSInteger)context tag:(NSString *)tag format:(NSString *)format, ...
{
    va_list args;
    
    if (format) {
        va_start(args, format);
        
        NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
        
        va_end(args);
        
        va_start(args, format);
        
        [self log:NO message:message level:self.logLevel flag:flag context:context file:__FILE__ function:__PRETTY_FUNCTION__ line:__LINE__ tag:tag];
        
        va_end(args);
    }
}

- (void)asyncLogWithFlag:(HCLogFlag)flag context:(NSInteger)context tag:(NSString *)tag format:(NSString *)format, ...
{
    va_list args;
    
    if (format) {
        va_start(args, format);
        
        NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
        
        va_end(args);
        
        va_start(args, format);
        
        [self log:YES message:message level:self.logLevel flag:flag context:context file:__FILE__ function:__PRETTY_FUNCTION__ line:__LINE__ tag:tag];
        
        va_end(args);
    }
}

- (void)log:(BOOL)isAsynchronous
    message:(NSString *)message
      level:(HCLogLevel)level
       flag:(HCLogFlag)flag
    context:(NSInteger)context
       file:(const char *)file
   function:(const char *)function
       line:(NSUInteger)line
        tag:(NSString *)tag
{
    HCLogMessage *logMessage = [[HCLogMessage alloc] initWithMessage:message
                                                               level:level
                                                                flag:flag
                                                             context:context
                                                                file:file
                                                            function:function
                                                                line:line
                                                                 tag:tag
                                                                date:[NSDate date]];
    
    [self enqueueLogMessage:logMessage asynchronously:isAsynchronous];
}

- (void)enqueueLogMessage:(HCLogMessage *)logMessage asynchronously:(BOOL)isAsynchronous
{
    dispatch_semaphore_wait(_loggingQueueSemaphore, DISPATCH_TIME_FOREVER);
    
    dispatch_block_t block = ^{ @autoreleasepool {
        [self lq_log:logMessage];
    } };
    
    if (isAsynchronous) {
        dispatch_async(_loggingQueue, block);
    } else {
        dispatch_sync(_loggingQueue, block);
    }
}

- (void)flushLog
{
    dispatch_sync(_loggingQueue, ^{ @autoreleasepool {
        [self lq_flush];
    } });
}

#pragma mark - logging queue

- (void)lq_addLogger:(id<HCLogger>)logger level:(HCLogLevel)level
{
    NSAssert(dispatch_get_specific(LoggingQueueKey), @"Must exec on logging queue!");
    
    for (HCLoggerNode *node in _loggers) {
        if (node->_logger == logger && node->_level == level) {
            // same logger, return
            return;
        }
    }
    
    dispatch_queue_t loggerQueue = nil;
    
    if ([logger respondsToSelector:@selector(loggerQueue)]) {
        // use own logger queue
        loggerQueue = [logger loggerQueue];
    }
    
    if (!loggerQueue) {
        
        const char *queueName = NULL;
        
        if ([logger respondsToSelector:@selector(loggerName)]) {
            queueName = [[logger loggerName] UTF8String];
        }
        
        // create queue use provided name, or use NULL
        loggerQueue = dispatch_queue_create(queueName, NULL);
    }

    HCLoggerNode *loggerNode = [HCLoggerNode nodeWithLogger:logger level:level queue:loggerQueue];
    [_loggers addObject:loggerNode];
}

- (void)lq_removeLogger:(id<HCLogger>)logger
{
    NSAssert(dispatch_get_specific(LoggingQueueKey), @"Must exec on logging queue!");
    
    HCLoggerNode *loggerNode = nil;
    
    for (HCLoggerNode *node in _loggers) {
        if (node->_logger == logger) {
            loggerNode = node;
            break;
        }
    }
    
    if (!loggerNode) {
        NSLog(@"Logger might not yet be added!");
        return;
    }
    
    [_loggers removeObject:loggerNode];
}

- (void)lq_log:(HCLogMessage *)logMessage
{
    NSAssert(dispatch_get_specific(LoggingQueueKey), @"Must exec on logging queue!");
    
    for (HCLoggerNode *loggerNode in _loggers) {
        // if logger should not write base on this level, find next.
        if (!(logMessage->_flag & loggerNode->_level)) {
            continue;
        }
        
        dispatch_group_async(_loggingGroup, loggerNode->_queue, ^{ @autoreleasepool {
            [loggerNode->_logger logMessage:logMessage];
        } });
    }
    
    dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER);
    
    dispatch_semaphore_signal(_loggingQueueSemaphore);
}

- (void)lq_flush
{
    NSAssert(dispatch_get_specific(LoggingQueueKey), @"Must exec on logging queue!");
    
    dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER);
}

@end
